/* 
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org

This file is part of GeoGebra.

This program is free software; you can redistribute it and/or modify it 
under the terms of the GNU General Public License as published by 
the Free Software Foundation.

 */

package org.geogebra.common.kernel;

import org.geogebra.common.kernel.geos.GeoNumeric;

/**
 * Slider mover for GeoNumeric and AlgoLocusSlider
 */
public class SliderMover extends PathMoverGeneric {

	private static final int BOUNDS_FIXED = 1;
	private static final int BOUNDS_INFINITE = 2;
	private static final int BOUNDS_FIXED_INFINITE = 3;
	private static final int BOUNDS_INFINITE_FIXED = 4;
	/** slider */
	private GeoNumeric slider;

	/**
	 * Creates new path mover for given path
	 * 
	 * @param path
	 *            slider
	 */
	public SliderMover(GeoNumeric path) {
		this.slider = path;
	}

	/**
	 * @param p
	 *            initial value
	 */
	public void init(GeoNumeric p) {
		init(p.getValue());
	}

	private void init(double param) {
		start_param = param;

		min_param = slider.getIntervalMin();
		max_param = slider.getIntervalMax();

		// make sure start_param is between min and max
		if (start_param < min_param || start_param > max_param) {
			param_extent = max_param - min_param;
			start_param = (start_param - min_param) % param_extent;
			if (start_param < min_param) {
				start_param += param_extent;
			}
		}

		if (min_param == Double.NEGATIVE_INFINITY) {
			if (max_param == Double.POSITIVE_INFINITY) {
				// (-infinite, +infinite)
				mode = BOUNDS_INFINITE;

				// infFunction(-1, 1)
				min_param = -1 + OPEN_BORDER_OFFSET;
				max_param = 1 - OPEN_BORDER_OFFSET;

				// transform start parameter to be in (-1, 1)
				start_param = PathNormalizer.inverseInfFunction(start_param);
			} else {
				// (-infinite, max_param]
				mode = BOUNDS_INFINITE_FIXED;
				start_param = 0;

				// max_param + infFunction(-1 ... 0)
				offset = max_param;
				min_param = -1 + OPEN_BORDER_OFFSET;
				max_param = 0;
			}
		} else {
			if (max_param == Double.POSITIVE_INFINITY) {
				// [min_param, +infinite)
				mode = BOUNDS_FIXED_INFINITE;
				start_param = 0;

				// min_param + infFunction(0 ... 1)
				offset = min_param;
				min_param = 0;
				max_param = 1 - OPEN_BORDER_OFFSET;
			} else {
				// [min_param, max_param]
				mode = BOUNDS_FIXED;
			}
		}

		param_extent = max_param - min_param;
		start_paramUP = start_param + param_extent;
		start_paramDOWN = start_param - param_extent;

		max_step_width = param_extent / MIN_STEPS;
		posOrientation = true;
		resetStartParameter();
	}

	/**
	 * @param p
	 *            store current position to p
	 */
	public void getCurrentPosition(GeoNumeric p) {
		calcPoint(p);
	}

	/**
	 * @param p
	 *            number to store result
	 * @return true if it was possible
	 */
	public boolean getNext(GeoNumeric p) {
		// check if we are in our interval
		boolean lineTo = true;
		last_param = curr_param;
		lastMaxBorderSet = maxBorderSet;
		lastMinBorderSet = minBorderSet;

		// in the last step we got outside a border and stopped there
		// now continue at the other border
		if (maxBorderSet) {
			curr_param = min_param;
			lineTo = false; // path.isClosedPath();
			maxBorderSet = false;
		} else if (minBorderSet) {
			curr_param = max_param;
			lineTo = false; // path.isClosedPath();
			minBorderSet = false;
		}

		// STANDARD CASE
		else {
			double new_param = curr_param + step_width;

			// new_param too big
			if (new_param >= max_param) {
				// slow down by making smaller steps
				while (new_param >= max_param && smallerStep()) {
					new_param = curr_param + step_width;
				}

				// max border reached
				if (new_param >= max_param) {
					new_param = max_param;
					maxBorderSet = true;
				}
			}

			// new_param too small
			else if (new_param <= min_param) {
				// slow down by making smaller steps
				while (new_param <= min_param && smallerStep()) {
					new_param = curr_param + step_width;
				}

				// min border reached
				if (new_param <= min_param) {
					new_param = min_param;
					minBorderSet = true;
				}
			}

			// set parameter
			curr_param = new_param;
		}

		// calculate point for current parameter
		calcPoint(p);

		return lineTo;
	}

	/**
	 * Updates path parameter of point p from curr_param
	 * 
	 * @param p
	 *            point to store result
	 */
	protected void calcPoint(GeoNumeric p) {
		double param;
		switch (mode) {
		case BOUNDS_FIXED:
			param = curr_param;
			break;

		case BOUNDS_INFINITE:
			param = PathNormalizer.infFunction(curr_param);
			break;

		case BOUNDS_FIXED_INFINITE:
		case BOUNDS_INFINITE_FIXED:
			param = offset + PathNormalizer.infFunction(curr_param);
			break;

		default:
			param = Double.NaN;
		}

		// PathParameter pp = p.getPathParameter();
		// pp.t = param;
		// path.pathChanged(p);
		// p.updateCoords();
		p.setValue(param);
	}

}
